package com.kmxd.ams.app.arrange;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.kmxd.ams.app.arrange.command.CommonQueryCmd;
import com.kmxd.ams.app.arrange.command.DataEmptyCheckCmd;
import com.kmxd.ams.app.event.AmsEventPublisher;
import com.kmxd.ams.client.archive.IArchiveFieldSvc;
import com.kmxd.ams.client.archive.IArchiveTypeSvc;
import com.kmxd.ams.client.archive.dto.ArchiveFieldQueryDTO;
import com.kmxd.ams.client.archive.vo.ArchiveFieldListVO;
import com.kmxd.ams.client.arrange.IDocumentSvc;
import com.kmxd.ams.client.arrange.IProjectSvc;
import com.kmxd.ams.client.arrange.IVolumeSvc;
import com.kmxd.ams.client.event.es.EsRemoveDataEvent;
import com.kmxd.ams.client.event.es.EsSaveDataEvent;
import com.kmxd.ams.client.event.es.EsUpdateDataEvent;
import com.kmxd.ams.client.event.summary.SummaryProjectEvent;
import com.kmxd.ams.core.common.constant.ArchConst;
import com.kmxd.ams.core.common.constant.Constants;
import com.kmxd.ams.core.common.constant.ErrorCode;
import com.kmxd.ams.core.common.enums.ArchStatusEnum;
import com.kmxd.ams.core.common.enums.DelFlagEnum;
import com.kmxd.ams.core.common.enums.ParamsValidateType;
import com.kmxd.ams.core.common.query.QueryGen;
import com.kmxd.ams.core.exception.BizTipException;
import com.kmxd.ams.core.exception.DataCheckException;
import com.kmxd.ams.core.exception.ParamsValidateException;
import com.kmxd.ams.core.util.*;
import com.kmxd.ams.infra.archive.entity.ArchiveType;
import com.kmxd.ams.infra.arrange.base.BaseBO;
import com.kmxd.ams.infra.arrange.entity.Document;
import com.kmxd.ams.infra.arrange.entity.Project;
import com.kmxd.ams.infra.arrange.entity.Volume;
import com.kmxd.ams.infra.arrange.mapper.ProjectMapper;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import org.slf4j.MDC;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 项目级 服务实现类
 *
 * @author ZuoLG
 * @since 2022-07-20
 */
@Service
@AllArgsConstructor
public class ProjectSvcImpl extends ServiceImpl<ProjectMapper, Project> implements IProjectSvc {

  private final AmsEventPublisher amsEventPublisher;
  private final CommonQueryCmd commonQueryCmd;
  private final IArchiveFieldSvc archiveFieldSvc;
  private final IArchiveTypeSvc archiveTypeSvc;
  private final IDocumentSvc documentSvc;
  private final IVolumeSvc volumeSvc;
  private ProjectMapper projectMapper;
  private final DataEmptyCheckCmd dataEmptyCheckCmd;

  @Override
  public Page<Project> selectPage(Page reqPage, Project req) {
    if (NumberUtil.equals(req.getStatus(), Constants.TRASH_KEY)) {
      return baseMapper.selectDelByPage(reqPage, req.getTableId());
    }
    if (ObjectUtil.isEmpty(req.getArchTypeId())) {
      BizTipException.throwException(ErrorCode.EXCEPTION, "档案库Id传入错误！");
    }
    ArchiveType archiveType = archiveTypeSvc.getById(req.getArchTypeId());

    req.setStatus(NumberUtil.equals(req.getStatus(), Constants.TRASH_KEY) ? null : req.getStatus());
    QueryWrapper<Project> wrapper = QueryGen.init(new QueryWrapper<Project>(), req);
    buildSearchWrapper(req, wrapper);
    wrapper.orderBy(
        StrUtil.isNotBlank(req.getField()),
        "ascend".equals(req.getOrder()),
        StringUtils.camelToUnderline(req.getField()));
    commonQueryCmd.buildFieldOrderQuery(req, wrapper);
    // 设置数据过滤条件
    String dataConditionSql = QueryUtil.buildDataSql(req.getDataCondition());
    if (CharSequenceUtil.isNotBlank(dataConditionSql)) {
      wrapper.apply(dataConditionSql);
    }

    LambdaQueryWrapper<Project> queryWrapper = wrapper.lambda();
    Set<Long> projIds = IdUtil.split(req.getProjIds());
    if (ObjectUtil.isNotEmpty(projIds)) {
      queryWrapper.in(Project::getId, projIds);
    }
    Page<Project> page = projectMapper.selectPage(reqPage, queryWrapper);
    // 查询统计信息
    if (BooleanUtil.isTrue(archiveType.getHaveVol())) {
      List<Document> documentList = documentSvc.statisticProjByVol(archiveType.getDocTableId());
      page.getRecords()
          .forEach(
              item -> {
                List<Document> projDocs =
                    documentList.stream()
                        .filter(x -> ObjectUtil.equals(x.getProjId(), item.getId()))
                        .collect(Collectors.toList());
                // 设置统计信息
                List<Document> volTotalArrange =
                    projDocs.stream()
                        .filter(
                            x ->
                                ObjectUtil.equals(x.getStatus(), ArchStatusEnum.S0.getCode())
                                    && ObjectUtil.isNotEmpty(x.getVolId()))
                        .collect(Collectors.toList());
                List<Document> volTotalArchive =
                    projDocs.stream()
                        .filter(
                            x ->
                                ObjectUtil.equals(x.getStatus(), ArchStatusEnum.S1.getCode())
                                    && ObjectUtil.isNotEmpty(x.getVolId()))
                        .collect(Collectors.toList());

                Document scatteredDocTotal =
                    projDocs.stream()
                        .filter(x -> ObjectUtil.isNull(x.getVolId()))
                        .findFirst()
                        .orElse(null);

                item.setVolTotalArrange(
                    ObjectUtil.isNotEmpty(volTotalArrange) ? volTotalArrange.size() : 0L);
                item.setVolTotalArchive(
                    ObjectUtil.isNotEmpty(volTotalArchive) ? volTotalArchive.size() : 0L);
                item.setScatteredDocTotal(
                    ObjectUtil.isNotEmpty(scatteredDocTotal) ? scatteredDocTotal.getTotal() : 0L);
              });
    }

    return page;
  }

  @Override
  public List<Project> selectList(Project req) {
    req.setStatus(NumberUtil.equals(req.getStatus(), Constants.TRASH_KEY) ? null : req.getStatus());
    QueryWrapper<Project> wrapper = QueryGen.init(new QueryWrapper<Project>(), req);
    wrapper.orderBy(
        StrUtil.isNotBlank(req.getField()),
        "ascend".equals(req.getOrder()),
        StringUtils.camelToUnderline(req.getField()));

    commonQueryCmd.buildFieldOrderQuery(req, wrapper);
    // 设置数据过滤条件
    String dataConditionSql = QueryUtil.buildDataSql(req.getDataCondition());
    if (CharSequenceUtil.isNotBlank(dataConditionSql)) {
      wrapper.apply(dataConditionSql);
    }

    LambdaQueryWrapper<Project> queryWrapper = wrapper.lambda();
    Set<Long> projIds = IdUtil.split(req.getProjIds());
    if (ObjectUtil.isNotEmpty(projIds)) {
      queryWrapper.in(Project::getId, projIds);
    }
    return projectMapper.selectList(queryWrapper);
  }

  @Override
  public List<Project> selectAllList(Project req) {
    QueryWrapper<Project> wrapper = QueryGen.init(new QueryWrapper<Project>(), req);
    wrapper.orderBy(
        StrUtil.isNotBlank(req.getField()),
        "ascend".equals(req.getOrder()),
        StringUtils.camelToUnderline(req.getField()));
    commonQueryCmd.buildFieldOrderQuery(req, wrapper);
    LambdaQueryWrapper<Project> queryWrapper = wrapper.lambda();
    return projectMapper.selectList(queryWrapper);
  }

  @Override
  public List<Project> selectListByIds(Project req) {
    if (StrUtil.isBlank(req.getProjIds())) {
      return Lists.newArrayList();
    }
    LambdaQueryWrapper<Project> queryWrapper = Wrappers.lambdaQuery();
    queryWrapper.in(Project::getId, IdUtil.split(req.getProjIds()));
    return projectMapper.selectList(queryWrapper);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void add(Project req) {
    checkData(req);
    if (ObjectUtil.isEmpty(req.getId())) {
      req.setId(IdWorker.getId());
    }
    dataEmptyCheckCmd.checkDataEmpty(req);

    // 设置默认值
    req.setDepartmentId(
        ObjectUtil.isNotEmpty(req.getDepartmentId())
            ? req.getDepartmentId()
            : SecurityUtil.getLoginUser().getDeptId());
    req.setDepartment(
        ObjectUtil.isNotEmpty(req.getDepartment())
            ? req.getDepartment()
            : SecurityUtil.getLoginUser().getDeptName());
    req.setDelFlag(DelFlagEnum.NO.getCode());
    req.setManageStatus(SecurityUtil.getManageStatus());
    projectMapper.insert(req);
    summary(req);
    MDC.put(ArchConst.UPDATE_DATA + req.getId(), JSONUtil.toJsonStr(req));
    // 索引数据到es
    amsEventPublisher.publishEvent(
        new EsSaveDataEvent(ImmutableList.of(req.getId()), req.getArchTypeId(), req.getTableId()));
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void edit(Project req) {
    checkData(req);
    dataEmptyCheckCmd.checkDataEmpty(req);
    req.setUpdateBy(
        ObjectUtil.isNotEmpty(SecurityUtil.getLoginUser())
            ? SecurityUtil.getUserId()
            : req.getUpdateBy());
    req.setUpdateTime(new Date());
    projectMapper.updateById(req);
    summary(req);
    MDC.put(ArchConst.UPDATE_DATA + req.getId(), JSONUtil.toJsonStr(req));
    // 索引数据到es
    amsEventPublisher.publishEvent(
        new EsUpdateDataEvent(
            ImmutableList.of(req.getId()), req.getArchTypeId(), req.getTableId()));
  }

  public void checkData(Project req) {
    if (ObjectUtil.isNull(req.getStatus())) {
      throw BizTipException.instance(ErrorCode.ARRANGE_STATUS_CAN_NOT_NULL, "档案状态不能为空");
    }
    if (!ImmutableSet.of(ArchStatusEnum.S0.getCode(), ArchStatusEnum.S1.getCode())
        .contains(req.getStatus())) {
      return;
    }
    ArchiveFieldQueryDTO qry = new ArchiveFieldQueryDTO();
    qry.setTableId(req.getTableId());
    if (ArchStatusEnum.S0.getCode().equals(req.getStatus())) {
      qry.setAttrRepeatWarn(Boolean.TRUE);
    }
    if (ArchStatusEnum.S1.getCode().equals(req.getStatus())) {
      qry.setAttrRepeatWarnArrange(Boolean.TRUE);
    }
    List<ArchiveFieldListVO> fieldList = archiveFieldSvc.selectList(qry);
    if (ObjectUtil.isEmpty(fieldList)) {
      return;
    }
    fieldList.forEach(
        item -> {
          QueryWrapper<Project> query = Wrappers.query();
          query
              .lambda()
              .eq(Project::getStatus, req.getStatus())
              .ne(Objects.nonNull(req.getId()), Project::getId, req.getId());
          query.eq(
              item.getDbFieldKey(),
              JSONUtil.parseObj(req).get(StringUtils.underlineToCamel(item.getDbFieldKey())));
          Long count = projectMapper.selectCount(query);
          long flag = null == count ? 0 : count;
          if (flag > 0) {
            throw ParamsValidateException.instance(
                StringUtils.camelToUnderline(item.getDbFieldKey()), ParamsValidateType.DATA_REPEAT);
          }
        });
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void remove(Project req) {
    Set<Long> idList = IdUtil.split(req.getProjIds());
    // 判断项目下是否有文件
    ArchiveType archiveType = archiveTypeSvc.getById(req.getArchTypeId());
    Document qryDoc = ConvertUtil.toBean(req, Document.class);
    qryDoc.setTableId(archiveType.getDocTableId());
    List<Document> docList = documentSvc.selectListByProjIds(qryDoc, true);
    if (!docList.isEmpty()) {
      throw DataCheckException.instance(ErrorCode.VALIDATE_ERROR, "检测到项目下有文件，请删除项目下文件，不能执行此操作！");
    }

    Volume qryVol = ConvertUtil.toBean(req, Volume.class);
    qryVol.setTableId(archiveType.getVolTableId());
    List<Volume> volumeList = volumeSvc.selectListByProjIds(qryVol);
    if (!volumeList.isEmpty()) {
      throw DataCheckException.instance(ErrorCode.VALIDATE_ERROR, "检测到项目下有案卷，请删除项目下案卷，不能执行此操作！");
    }

    idList.forEach(
        item -> {
          DynamicTableNameUtil.setTableName(archiveType.getProjTableId());
          Project project = projectMapper.selectById(item);
          projectMapper.deletion(req.getTableId(), ListUtil.of(item));
          if (ObjectUtil.isEmpty(project)) {
            return;
          }
          project.setArchTypeId(req.getArchTypeId());
          project.setTableId(req.getTableId());
        });

    // 删除es数据
    amsEventPublisher.publishEvent(
        new EsRemoveDataEvent(req.getProjIds(), req.getArchTypeId(), req.getTableId()));
  }

  @Override
  public Project view(Project req) {
    return projectMapper.selectById(req.getId());
  }

  @Override
  public long count(Project req) {
    QueryWrapper<Project> query = Wrappers.query();
    QueryGen.init(query, req);
    buildSearchWrapper(req, query);
    Long count = projectMapper.selectCount(query);
    return null == count ? 0 : count;
  }

  private void buildSearchWrapper(Project req, QueryWrapper<Project> query) {
    if (StrUtil.isBlank(req.getKeywords())) {
      return;
    }
    ArchiveFieldQueryDTO qryField = new ArchiveFieldQueryDTO();
    qryField.setArchTypeId(req.getArchTypeId());
    qryField.setTableId(req.getTableId());
    qryField.setEsIndex(true);

    List<ArchiveFieldListVO> fieldList = archiveFieldSvc.selectList(qryField);
    // 过滤的list为空，会报空指针异常
    List<String> fields =
        fieldList.stream().map(ArchiveFieldListVO::getDbFieldKey).collect(Collectors.toList());
    QueryUtil.buildSearchWrapper(req.getKeywords(), fields, query);
  }

  @Override
  public Boolean cleanTrash(BaseBO req) {
    return true;
  }

  @Override
  public Boolean recoveryData(BaseBO req) {
    Set<Long> ids = IdUtil.split(req.getProjIds());
    this.baseMapper.recoveryData(req.getTableId(), ids);
    return null;
  }

  /**
   * 汇总信息
   *
   * @param req
   */
  @Override
  public void summary(Project req) {

    ArchiveType archiveType = archiveTypeSvc.getById(req.getArchTypeId());

    amsEventPublisher.publishEvent(
        new SummaryProjectEvent(
            req.getId(),
            req.getArchTypeId(),
            archiveType.getProjTableId(),
            archiveType.getDocTableId(),
            archiveType.getVolTableId(),
            archiveType.getBoxTableId()));
  }
}
